package Socket;

import java.io.*;
import java.net.*;
import java.util.*;

import ExecuteEngine.Operation;
import ExecuteEngine.Special_op;
import GUI.ServerFrame;

import sql.IndexItem;
import sql.User_Manager;

public class Server {

	private ServerSocket serverSocket;
	private User_Manager um;
	private ServerFrame sf;
	
	public Server(ServerFrame sf) throws IOException {
		this.sf = sf;
		um = new User_Manager();
		serverSocket = new ServerSocket(1001);
		runServer();
	}

	private void runServer() {
		  while (true) {
			  try {
				  Socket socket = serverSocket.accept();
				  new ServerThread(um, socket, sf).start();
			  } catch (IOException ioException) {
				  ioException.printStackTrace();
			  }
		  }
	}
	
}

class ServerThread extends Thread implements Protocol{

	Socket sock;
	int UID;
	User_Manager um;
	ServerFrame sf;
	
	ObjectInputStream in;
	ObjectOutputStream out;
	
	public ServerThread(User_Manager um, Socket socket, ServerFrame sf) {
		this.um = um;
		sock = socket;
		this.sf = sf;
		
		try {
			in = new ObjectInputStream(sock.getInputStream());
			out = new ObjectOutputStream(sock.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private synchronized int recvLoginInfo() throws IOException {
		/* Return:
		 * 1	login
		 * 0	while continue
		 * -1	end	
		 */
		int cmd = in.readInt();
		if ( cmd==CMD_REGISTER ) { // receive register cmd
			String un = RecvString();
			String pwd = RecvString();
			if (um.register(un, pwd)) {
				out.writeInt(RET_REGISTER_SUCCEED);
				out.flush();
				sf.addMessageItem("New registeration succeed: "+un +", "+pwd);
			} else {
				out.writeInt(RET_REGISTER_FAIL);
				out.flush();
				sf.addMessageItem("New registeration failed.");
			}
		} else if ( cmd == CMD_LOGIN ) { // receive login cmd
			String un = RecvString();
			String pwd = RecvString();
			UID = um.login(un, pwd);
			out.writeInt(UID);
			out.flush();
			if (UID>=0){
				sf.addMessageItem("UID"+UID+": Login succeed.");
				return 1;
			}
			return 0;
		} else { // other cmd received. thread ends.
			sf.addMessageItem("UID"+UID+": Wrong cmd received. Drop Connection.");
			return -1;
		}
		return 0;
	}
	
	Operation op;
	private synchronized boolean recvCmdAndDo() throws IOException {
		int cmd = in.readInt();
		if (cmd == CMD_QUERY) {
			int uid = in.readInt();
			String cmdline = RecvString();
			try {
				op = um.queryExec(cmdline, uid);
			} catch (Error e) {
				out.writeInt(RET_QUERY_ERROR);
				SendString(e.getMessage());
				out.flush();
				return false;
			}
			if (op == null) {
				out.writeInt(0);
				out.flush();
			} else {
				out.writeInt(op.type);
				out.flush();
			}
			sf.addMessageItem("UID"+uid+": a query is required.");
		} else if (cmd == CMD_GETOP) {
			int uid = in.readInt();
			if (op == null || op.type != Operation.SPECIALOP) {
				out.writeInt(-1);
				out.flush();
			} else {
				out.writeInt( ((Special_op)op).op );
				out.flush();
				sf.addMessageItem("UID"+uid+": Tree Updates.");
			}
		} else if (cmd == CMD_GETREL) {
			if (op == null || op.type != Operation.SPECIALOP) {
				SendString(null);
				out.flush();
			} else {
				SendString( ((Special_op)op).rel );
				out.flush();
			}
		} else if (cmd == CMD_GETINDEXITEM) {
			if (op == null || op.type != Operation.SPECIALOP) {
				SendBytes(null);
				out.flush();
			} else {
				IndexItem it = ((Special_op)op).index;
				if (it == null)
					SendBytes(null);
				else
					SendBytes( it.getBytes() );
				out.flush();
			}
		} else if (cmd == CMD_GETVIEW) {
			if (op == null || op.type != Operation.SPECIALOP) {
				SendString(null);
				out.flush();
			} else {
				SendString( ((Special_op)op).view);
				out.flush();
			}
		} else if (cmd == CMD_GETTABLES) {
			int uid = in.readInt();
			List<String> tableList = um.getTables(uid);
			out.writeInt(tableList.size());
			for (int i=0; i<tableList.size(); ++i)
				SendString(tableList.get(i));
			out.flush();
			sf.addMessageItem("UID"+uid+": Get Table Required.");
		} else if (cmd == CMD_GETINDEXES) {
			int uid = in.readInt();
			List<IndexItem> indexList = um.getIndexes(uid);
			out.writeInt(indexList.size());
			for (int i=0; i<indexList.size(); ++i)
				SendBytes(indexList.get(i).getBytes());
			out.flush();
			sf.addMessageItem("UID"+uid+": Get Index Required.");
		} else if (cmd == CMD_GETVIEWS) {
			int uid = in.readInt();
			List<String> viewList = um.getViews(uid);
			out.writeInt(viewList.size());
			for (int i=0; i<viewList.size(); ++i)
				SendString(viewList.get(i));
			out.flush();
			sf.addMessageItem("UID"+uid+": Get Views Required.");
		} else if (cmd == CMD_QUERYOPEN) {
			int uid = in.readInt();
			if (op != null)
				op.Open();
			sf.addMessageItem("UID"+uid+": Operation open required.");
/*		} else if (cmd == CMD_QUERYHASNEXT) {
			int uid = in.readInt();
			Operation op = ops.get(uid);
			if (op != null && op.hasNext()) {
				out.writeInt(1);
				out.flush();
			} else {
				out.writeInt(0);
				out.flush();
			}
*/		} else if (cmd == CMD_QUERYGETNEXT) {
			int uid = in.readInt();
			if (op != null && op.hasNext() ) {
				SendBytes(op.GetNext().getBytes());
				out.flush();
				sf.addMessageItem("UID"+uid+": Get a Tuple of query result.");
			} else  {
				SendBytes(null);
				out.flush();
			}
		} else if (cmd == CMD_QUERYCLOSE) {
			int uid = in.readInt();
			if (op != null) {
				op.Close();
				op = null;
				sf.addMessageItem("UID"+uid+": An Operation is closed.");
			}
		} else if (cmd == CMD_GETTITLES) {
			int uid = in.readInt();
			String table = RecvString();
			List<String> titles = um.GetTitles(uid, table);
			out.writeInt(titles.size());
			for (int i=0; i<titles.size();++i)
				SendString(titles.get(i));
			out.flush();
		}
		return false;
	}
	
	public void run() {
		try {
			for(;;) { // receive login or register cmd
				int ret = recvLoginInfo();
				if (ret == 1)
					break;
				if (ret == -1)
					return;
			}
		} catch (EOFException e) {
			sf.addMessageItem("A Client disconnected.");
			return;
		} catch (SocketException e) {
			sf.addMessageItem("UID"+UID+" has unlogged.");
			return;
		} catch (IOException e) {
			e.printStackTrace();
		}
		for(;;) { // receive and do cmds
			try {
				if (recvCmdAndDo()) 
					break;
			} catch (EOFException e) {
				sf.addMessageItem("UID"+UID+" has unlogged.");
				return;
			} catch (SocketException e) {
				sf.addMessageItem("UID"+UID+" has unlogged.");
				return;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void SendBytes(byte[] bytes) throws IOException {
		if (bytes==null) {
			out.writeInt(0);
			return;
		}
		out.writeInt(bytes.length);
		if (bytes.length!=0)
			out.write(bytes);
	}
	
	private void SendString(String s) throws IOException {
		if (s==null) {
			out.writeInt(0);
			return;
		}
		byte[] bytes = s.getBytes();
		out.writeInt(bytes.length);
		out.write(bytes);
	}

/*	private byte[] RecvBytes() throws IOException {
		int length = in.readInt();
		if (length == 0)
			return null;
		byte[] bytes = new byte[length];
		in.read(bytes);
		return bytes;
	}
*/	
	private String RecvString() throws IOException {
		int length = in.readInt();
		if (length == 0)
			return null;
		byte[] bytes = new byte[length];
		in.read(bytes);
		return new String(bytes);
	}

}